package com.jfirer.mvc.binder.impl;

import com.jfirer.baseutil.reflect.ReflectUtil;
import com.jfirer.baseutil.reflect.ValueAccessor;
import com.jfirer.mvc.binder.ContainerBinder;
import com.jfirer.mvc.binder.DataBinder2;
import com.jfirer.mvc.binder.SingleValueBinder;
import com.jfirer.mvc.binder.impl.base.*;

import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class BeanBinder2 implements ContainerBinder
{
    private Class      ckass;
    private Property[] properties;

    class Property
    {
        ValueAccessor valueAccessor;
        String        prefix;
        DataBinder2   dataBinder2;
        boolean       isContainer = false;
        Field         field;
    }

    public BeanBinder2(String prefix, Class ckass)
    {
        this.ckass = ckass;
        List<Property> list = new LinkedList<Property>();
        while (ckass != Object.class)
        {
            for (Field each : ckass.getDeclaredFields())
            {
                Property property = new Property();
                if (prefix == null)
                {
                    property.prefix = each.getName();
                }
                else
                {
                    property.prefix = prefix + '[' + each.getName() + ']';
                }
                property.valueAccessor = new ValueAccessor(each);
                property.field = each;
                if (each.getType() == int.class || each.getType() == Integer.class)
                {
                    property.dataBinder2 = new IntegerBinder(property.prefix);
                }
                else if (each.getType() == long.class || each.getType() == Long.class)
                {
                    property.dataBinder2 = new LongBinder(property.prefix);
                }
                else if (each.getType() == short.class || each.getType() == Short.class)
                {
                    property.dataBinder2 = new ShortBinder(property.prefix);
                }
                else if (each.getType() == byte.class || each.getType() == Byte.class)
                {
                    property.dataBinder2 = new ByteBinder(property.prefix);
                }
                else if (each.getType() == float.class || each.getType() == Float.class)
                {
                    property.dataBinder2 = new FloatBinder(property.prefix);
                }
                else if (each.getType() == double.class || each.getType() == Double.class)
                {
                    property.dataBinder2 = new DoubleBinder(property.prefix);
                }
                else if (each.getType() == boolean.class || each.getType() == Boolean.class)
                {
                    property.dataBinder2 = new BooleanBinder(property.prefix);
                }
                else if (each.getType() == String.class)
                {
                    property.dataBinder2 = new StringBinder(property.prefix);
                }
                else if (Enum.class.isAssignableFrom(each.getType()))
                {
                    property.dataBinder2 = new EnumBinder(property.prefix, (Class<? extends Enum>) each.getType());
                }
                else
                {
                    property.isContainer = true;
                }
                list.add(property);
            }
            ckass = ckass.getSuperclass();
        }
        properties = list.toArray(new Property[list.size()]);
    }

    public Object bindMulti(Map<String, String[]> map)
    {
        return bindInternal(map, false);
    }

    private Object bindInternal(Map<?, ?> map, boolean single)
    {
        try
        {
            Object container = ckass.newInstance();
            for (Property property : properties)
            {
                ValueAccessor valueAccessor = property.valueAccessor;
                String        prefix        = property.prefix;
                if (property.isContainer)
                {
                    Object origin, propertyValue;
                    origin = propertyValue = valueAccessor.get(container);
                    for (Map.Entry entry : map.entrySet())
                    {
                        String key = (String) entry.getKey();
                        if ((key.startsWith(prefix) && key.charAt(prefix.length()) == '['))
                        {
                            ContainerBinder dataBinder2 = (ContainerBinder) property.dataBinder2;
                            if (dataBinder2 == null)
                            {
                                Field field = property.field;
                                if (field.getType().isArray())
                                {
                                    property.dataBinder2 = dataBinder2 = new ArrayBinder2(prefix, property.field.getType());
                                }
                                else if (List.class.isAssignableFrom(field.getType()))
                                {
                                    property.dataBinder2 = dataBinder2 = new ListBinder(prefix, property.field.getGenericType());
                                }
                                else
                                {
                                    property.dataBinder2 = dataBinder2 = new BeanBinder2(prefix, field.getType());
                                }
                            }
                            if (single)
                            {
                                propertyValue = dataBinder2.bind((String) entry.getKey(), (String) entry.getValue(), propertyValue);
                            }
                            else
                            {
                                propertyValue = dataBinder2.bind((String) entry.getKey(), (String[]) entry.getValue(), propertyValue);
                            }
                        }
                    }
                    if (origin != propertyValue)
                    {
                        valueAccessor.setObject(container, propertyValue);
                    }
                }
                else
                {
                    Object value = map.get(prefix);
                    if (value == null)
                    {
                        continue;
                    }
                    SingleValueBinder binder2 = (SingleValueBinder) property.dataBinder2;
                    if (single)
                    {
                        Object propertyValue = binder2.bind(prefix, (String) value);
                        valueAccessor.setObject(container, propertyValue);
                    }
                    else
                    {
                        Object propertyValue = binder2.bind(prefix, (String[]) value);
                        valueAccessor.setObject(container, propertyValue);
                    }
                }
            }
            return container;
        }
        catch (Exception e)
        {
            ReflectUtil.throwException(e);
            return null;
        }
    }

    @Override
    public Object bind(Map<String, String> map)
    {
        return bindInternal(map, true);
    }

    @Override
    public Object bind(String name, String[] value, Object container)
    {
        return bindInternal(name, value, container, false);
    }

    @Override
    public Object bind(String name, String value, Object container)
    {
        return bindInternal(name, value, container, true);
    }

    private Object bindInternal(String name, Object value, Object container, boolean single)
    {
        try
        {
            container = container == null ? ckass.newInstance() : container;
            for (Property each : properties)
            {
                String        prefix        = each.prefix;
                ValueAccessor valueAccessor = each.valueAccessor;
                if (each.isContainer)
                {
                    Object propertyValue = valueAccessor.get(container);
                    if ((name.startsWith(prefix) && name.charAt(prefix.length()) == '['))
                    {
                        ContainerBinder dataBinder2 = (ContainerBinder) each.dataBinder2;
                        if (dataBinder2 == null)
                        {
                            Field field = each.field;
                            if (field.getType().isArray())
                            {
                                each.dataBinder2 = dataBinder2 = new ArrayBinder2(prefix, field.getType());
                            }
                            else if (List.class.isAssignableFrom(field.getType()))
                            {
                            }
                            else
                            {
                                each.dataBinder2 = dataBinder2 = new BeanBinder2(prefix, field.getType());
                            }
                        }
                        if (single)
                        {
                            propertyValue = dataBinder2.bind((String) name, (String) value, propertyValue);
                        }
                        else
                        {
                            propertyValue = dataBinder2.bind((String) name, (String[]) value, propertyValue);
                        }
                        valueAccessor.setObject(container, propertyValue);
                        break;
                    }
                }
                else
                {
                    if (name.equals(prefix))
                    {
                        Object propertyValue;
                        if (single)
                        {
                            propertyValue = ((SingleValueBinder) each.dataBinder2).bind(prefix, (String) value);
                        }
                        else
                        {
                            propertyValue = ((SingleValueBinder) each.dataBinder2).bind(prefix, (String[]) value);
                        }
                        valueAccessor.setObject(container, propertyValue);
                        break;
                    }
                }
            }
            return container;
        }
        catch (Exception e)
        {
            ReflectUtil.throwException(e);
            return null;
        }
    }
}
